<template>
	<view>
		<view v-if="hasPermission">
			<tn-notice-bar v-show="tip" style="position: absolute; top: -120rpx; left: -80rpx" :list="['录音中']" mode="horizontal"></tn-notice-bar>
			<!-- <tn-button backgroundColor="#01BEFF" shape="icon" size="lg"  @longpress="recStart" @touchend="recStop">
				<text  class="tn-icon-voice tn-color-white" style="font-size: 26px"></text>
			</tn-button> -->
			<tn-button v-show="!tip" backgroundColor="#01BEFF" shape="icon" size="lg" @tap="recStart">
				<text class="tn-color-white">语音识别</text>
			</tn-button>

			<tn-button v-show="tip" backgroundColor="#01BEFF" shape="icon" size="lg" @tap="recStop">
				<text class="tn-color-white">停止</text>
			</tn-button>
		</view>
		<view v-else>
			<tn-button backgroundColor="#FBBD12" shape="icon" size="lg" @tap="recReq">
				<text class="tn-color-white">授权</text>
			</tn-button>
		</view>
		
		<!-- <view>
			<tn-button backgroundColor="#FBBD12" shape="icon" size="lg" @tap="chooseAndUploadFile">
				<text class="tn-color-white">模拟</text>
			</tn-button>
		</view> -->
	</view>
</template>

<script>
import config from '@/config';
/**这里是逻辑层**/
//必须引入的Recorder核心（文件路径是 /src/recorder-core.js 下同），使用import、require都行
import Recorder from 'recorder-core'; //注意如果未引用Recorder变量，可能编译时会被优化删除（如vue3 tree-shaking），请改成 import 'recorder-core'，或随便调用一下 Recorder.a=1 保证强引用

//必须引入的RecordApp核心文件（文件路径是 /src/app-support/app.js）
import RecordApp from 'recorder-core/src/app-support/app';

//所有平台必须引入的uni-app支持文件（如果编译出现路径错误，请把@换成 ../../ 这种）
import '@/uni_modules/Recorder-UniCore/app-uni-support.js';

/** 需要编译成微信小程序时，引入微信小程序支持文件 **/
// #ifdef MP-WEIXIN
import 'recorder-core/src/app-support/app-miniProgram-wx-support.js';
// #endif

/** H5、小程序环境中：引入需要的格式编码器、可视化插件，App环境中在renderjs中引入 **/
// 注意：如果App中需要在逻辑层中调用Recorder的编码/转码功能，需要去掉此条件编译，否则会报未加载编码器的错误
// #ifdef H5 || MP-WEIXIN
//按需引入你需要的录音格式支持文件，如果需要多个格式支持，把这些格式的编码引擎js文件统统引入进来即可
// import 'recorder-core/src/engine/mp3';
// import 'recorder-core/src/engine/mp3-engine'; //如果此格式有额外的编码引擎（*-engine.js）的话，必须要加上

import 'recorder-core/src/engine/wav';

//可选的插件支持项，把需要的插件按需引入进来即可
import 'recorder-core/src/extensions/waveview';
// #endif

// ... 这后面写页面代码，用选项式API风格（vue2、vue3）、setup组合式API风格（仅vue3）都可以

/**在逻辑层中编写**/
//import ... 上面那些import代码

//var vue3This=getCurrentInstance().proxy; //当用vue3 setup组合式 API (Composition API) 编写时，直接在import后面取到当前实例this，在需要this的地方传vue3This变量即可，其他的和选项式 API (Options API) 没有任何区别；import {getCurrentInstance} from 'vue'；详细可以参考Demo项目中的 page_vue3____composition_api.vue

//RecordApp.UniNativeUtsPlugin={ nativePlugin:true };  //App中启用配套的原生录音插件支持，配置后会使用原生插件进行录音，没有原生插件时依旧使用renderjs H5录音
//App中提升后台录音的稳定性：配置了原生插件后，可配置 `RecordApp.UniWithoutAppRenderjs=true` 禁用renderjs层音频编码（WebWorker加速），变成逻辑层中直接编码（但会降低逻辑层性能），后台运行时可避免部分手机WebView运行受限的影响
//App中提升后台录音的稳定性：需要启用后台录音保活服务（iOS不需要，参考录音权限配置），Android 9开始，锁屏或进入后台一段时间后App可能会被禁止访问麦克风导致录音静音、无法录音（renderjs中H5录音也受影响），请调用配套原生插件的`androidNotifyService`接口，或使用第三方保活插件

export default {
	data() {
		return {
			tip: false,
			hasPermission: false
		};
	}, //视图没有引用到的变量无需放data里，直接this.xxx使用

	mounted() {
		this.isMounted = true;
		//页面onShow时【必须调用】的函数，传入当前组件this
		RecordApp.UniPageOnShow(this);
		// setTimeout(() => {
		// 	this.recReq();
		// }, 1000);
	},
	onShow() {
		//onShow可能比mounted先执行，页面可能还未准备好
		if (this.isMounted) RecordApp.UniPageOnShow(this);
	},

	methods: {
		chooseAndUploadFile() {
			uni.chooseFile({
				count: 1, // 选择文件的数量
				type: 'all', // 可选择的文件类型
				success: (res) => {
					const filePath = res.tempFiles[0].path;
					uni.uploadFile({
						url: config.baseUrl + '/jy/jyBase/convertSpeechToText', // 替换为实际的上传接口地址
						filePath: filePath,
						name: 'file',
						success: (uploadRes) => {
							console.log('uploadRes',uploadRes);
							if (uploadRes.statusCode === 200) {
								this.formatData(uploadRes)
								this.uploadResult = '文件上传成功';
							} else {
								this.uploadResult = '文件上传失败';
							}
						},
						fail: (err) => {
							this.uploadResult = `上传失败: ${err.errMsg}`;
						}
					});
				},
				fail: (err) => {
					this.uploadResult = `选择文件失败: ${err.errMsg}`;
				}
			});
		},
		formatData(res){
			const json = JSON.parse(res.data)
			this.result = json;
			if(json.code === 200){
				this.$emit("getData",json.data.data)
			} else {
				uni.showToast({
					title: json.msg
				})
			}	
		},
		upFile(file) {
			uni.showLoading();
			//H5中直接使用浏览器提供的File接口构造一个文件
			uni.uploadFile({
				url: config.baseUrl + '/jy/jyBase/convertSpeechToText',
				file,
				name: 'file',
				formData: {
					// ... 其他表单参数 ...
				},
				success: (res) => {
					if(res.statusCode === 200){
						this.formatData(res)
					}else{
						uni.showToast({
							title: "语音上传失败，请稍后重试"
						})
					}
					
					
				},
				complete: () => {
					uni.hideLoading();
				}
			});
		},
		//请求录音权限
		recReq() {
			//编译成App时提供的授权许可（编译成H5、小程序为免费授权可不填写）；如果未填写授权许可，将会在App打开后第一次调用请求录音权限时，弹出“未获得商用授权时，App上仅供测试”提示框
			//RecordApp.UniAppUseLicense='我已获得UniAppID=*****的商用授权';

			//RecordApp.RequestPermission_H5OpenSet={ audioTrackSet:{ noiseSuppression:true,echoCancellation:true,autoGainControl:true } }; //这个是Start中的audioTrackSet配置，在h5（H5、App+renderjs）中必须提前配置，因为h5中RequestPermission会直接打开录音

			RecordApp.UniWebViewActivate(this); //App环境下必须先切换成当前页面WebView
			RecordApp.RequestPermission(
				() => {
					console.log('已获得录音权限，可以开始录音了');
					this.hasPermission = true;
				},
				(msg, isUserNotAllow) => {
					if (isUserNotAllow) {
						this.hasPermission = false;
						uni.showToast({
							title:"授权失败，请返回重试",
							icon: 'none'
						})
						//用户拒绝了录音权限
						//这里你应当编写代码进行引导用户给录音权限，不同平台分别进行编写
					}
					console.error('请求录音权限失败：' + msg);
					uni.showToast({
						title:msg,
						icon: 'none'
					})
				}
			);
		},

		//开始录音
		recStart() {
			this.tip = true;
			//Android App如果要后台录音，需要启用后台录音保活服务（iOS不需要），需使用配套原生插件、或使用第三方保活插件
			//RecordApp.UniNativeUtsPluginCallAsync("androidNotifyService",{ title:"正在录音" ,content:"正在录音中，请勿关闭App运行" }).then(()=>{...}).catch((e)=>{...}) 注意必须RecordApp.RequestPermission得到权限后调用

			//录音配置信息
			var set = {
				type: 'wav',
				sampleRate: 16000,
				bitRate: 16, //mp3格式，指定采样率hz、比特率kbps，其他参数使用默认配置；注意：是数字的参数必须提供数字，不要用字符串；需要使用的type类型，需提前把格式支持文件加载进来，比如使用wav格式需要提前加载wav.js编码引擎
				/*,audioTrackSet:{ //可选，如果需要同时播放声音（比如语音通话），需要打开回声消除（并不一定会生效；打开后声音可能会从听筒播放，部分环境下（如小程序、App原生插件）可调用接口切换成扬声器外放）
                //注意：H5、App+renderjs中需要在请求录音权限前进行相同配置RecordApp.RequestPermission_H5OpenSet后此配置才会生效
                echoCancellation:true,noiseSuppression:true,autoGainControl:true} */
				onProcess: (buffers, powerLevel, duration, sampleRate, newBufferIdx, asyncEnd) => {
					//全平台通用：可实时上传（发送）数据，配合Recorder.SampleData方法，将buffers中的新数据连续的转换成pcm上传，或使用mock方法将新数据连续的转码成其他格式上传，可以参考Recorder文档里面的：Demo片段列表 -> 实时转码并上传-通用版；基于本功能可以做到：实时转发数据、实时保存数据、实时语音识别（ASR）等

					//注意：App里面是在renderjs中进行实际的音频格式编码操作，此处的buffers数据是renderjs实时转发过来的，修改此处的buffers数据不会改变renderjs中buffers，所以不会改变生成的音频文件，可在onProcess_renderjs中进行修改操作就没有此问题了；如需清理buffers内存，此处和onProcess_renderjs中均需要进行清理，H5、小程序中无此限制
					//注意：如果你要用只支持在浏览器中使用的Recorder扩展插件，App里面请在renderjs中引入此扩展插件，然后在onProcess_renderjs中调用这个插件；H5可直接在这里进行调用，小程序不支持这类插件；如果调用插件的逻辑比较复杂，建议封装成js文件，这样逻辑层、renderjs中直接import，不需要重复编写

					//H5、小程序等可视化图形绘制，直接运行在逻辑层；App里面需要在onProcess_renderjs中进行这些操作
					// #ifdef H5 || MP-WEIXIN
					if (this.waveView) this.waveView.input(buffers[buffers.length - 1], powerLevel, sampleRate);
					// #endif

					/*实时释放清理内存，用于支持长时间录音；在指定了有效的type时，编码器内部可能还会有其他缓冲，必须同时提供takeoffEncodeChunk才能清理内存，否则type需要提供unknown格式来阻止编码器内部缓冲，App的onProcess_renderjs中需要进行相同操作
                if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //重新录音了就重置
                for(var i=this.clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
                this.clearBufferIdx=newBufferIdx; */
				},
				onProcess_renderjs: `function(buffers,powerLevel,duration,sampleRate,newBufferIdx,asyncEnd){
                //App中在这里修改buffers会改变生成的音频文件，但注意：buffers会先转发到逻辑层onProcess后才会调用本方法，因此在逻辑层的onProcess中需要重新修改一遍
                //本方法可以返回true，renderjs中的onProcess将开启异步模式，处理完后调用asyncEnd结束异步，注意：这里异步修改的buffers一样的不会在逻辑层的onProcess中生效
                //App中是在renderjs中进行的可视化图形绘制，因此需要写在这里，this是renderjs模块的this（也可以用This变量）；如果代码比较复杂，请直接在renderjs的methods里面放个方法xxxFunc，这里直接使用this.xxxFunc(args)进行调用
                if(this.waveView) this.waveView.input(buffers[buffers.length-1],powerLevel,sampleRate);

                /*和onProcess中一样进行释放清理内存，用于支持长时间录音
                if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //重新录音了就重置
                for(var i=this.clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
                this.clearBufferIdx=newBufferIdx; */
            }`,
				onProcessBefore_renderjs: `function(buffers,powerLevel,duration,sampleRate,newBufferIdx){
                //App中本方法会在逻辑层onProcess之前调用，因此修改的buffers会转发给逻辑层onProcess，本方法没有asyncEnd参数不支持异步处理
                //一般无需提供本方法只用onProcess_renderjs就行，renderjs的onProcess内部调用过程：onProcessBefore_renderjs -> 转发给逻辑层onProcess -> onProcess_renderjs
            }`,

				takeoffEncodeChunk: true
					? null
					: (chunkBytes) => {
							//全平台通用：实时接收到编码器编码出来的音频片段数据，chunkBytes是Uint8Array二进制数据，可以实时上传（发送）出去
							//App中如果未配置RecordApp.UniWithoutAppRenderjs时，建议提供此回调，因为录音结束后会将整个录音文件从renderjs传回逻辑层，由于uni-app的逻辑层和renderjs层数据交互性能实在太拉跨了，大点的文件传输会比较慢，提供此回调后可避免Stop时产生超大数据回传
							//App中使用原生插件时，可方便的将数据实时保存到同一文件，第一帧时append:false新建文件，后面的append:true追加到文件
							//RecordApp.UniNativeUtsPluginCallAsync("writeFile",{path:"xxx.mp3",append:回调次数!=1, dataBase64:RecordApp.UniBtoa(chunkBytes.buffer)}).then(...).catch(...)
					  },
				takeoffEncodeChunk_renderjs: true
					? null
					: `function(chunkBytes){
                //App中这里可以做一些仅在renderjs中才生效的事情，不提供也行，this是renderjs模块的this（也可以用This变量）
            }`,

				start_renderjs: `function(){
                //App中可以放一个函数，在Start成功时renderjs中会先调用这里的代码，this是renderjs模块的this（也可以用This变量）
                //放一些仅在renderjs中才生效的事情，比如初始化，不提供也行
            }`,
				stop_renderjs: `function(arrayBuffer,duration,mime){
                //App中可以放一个函数，在Stop成功时renderjs中会先调用这里的代码，this是renderjs模块的this（也可以用This变量）
                //放一些仅在renderjs中才生效的事情，不提供也行
            }`
			};

			RecordApp.UniWebViewActivate(this); //App环境下必须先切换成当前页面WebView
			RecordApp.Start(
				set,
				() => {
					console.log('已开始录音');
					//【稳如老狗WDT】可选的，监控是否在正常录音有onProcess回调，如果长时间没有回调就代表录音不正常
					//var wdt=this.watchDogTimer=setInterval ... 请参考示例Demo的main_recTest.vue中的watchDogTimer实现

					//创建音频可视化图形绘制，App环境下是在renderjs中绘制，H5、小程序等是在逻辑层中绘制，因此需要提供两段相同的代码
					//view里面放一个canvas，canvas需要指定宽高（下面style里指定了300*100）
					//<canvas type="2d" class="recwave-WaveView" style="width:300px;height:100px"></canvas>
					RecordApp.UniFindCanvas(
						this,
						['.recwave-WaveView'],
						`
                this.waveView=Recorder.WaveView({compatibleCanvas:canvas1, width:300, height:100});
            `,
						(canvas1) => {
							this.waveView = Recorder.WaveView({ compatibleCanvas: canvas1, width: 300, height: 100 });
						}
					);
				},
				(msg) => {
					console.error('开始录音失败：' + msg);
				}
			);
		},

		//暂停录音
		recPause() {
			if (RecordApp.GetCurrentRecOrNull()) {
				RecordApp.Pause();
				console.log('已暂停');
			}
		},
		//继续录音
		recResume() {
			if (RecordApp.GetCurrentRecOrNull()) {
				RecordApp.Resume();
				console.log('继续录音中...');
			}
		},

		//停止录音
		recStop() {
			this.tip = false;
			//RecordApp.UniNativeUtsPluginCallAsync("androidNotifyService",{ close:true }) //关闭Android App后台录音保活服务

			RecordApp.Stop(
				(arrayBuffer, duration, mime) => {
					//全平台通用：arrayBuffer是音频文件二进制数据，可以保存成文件或者发送给服务器
					//App中如果在Start参数中提供了stop_renderjs，renderjs中的函数会比这个函数先执行

					//注意：当Start时提供了takeoffEncodeChunk后，你需要自行实时保存录音文件数据，因此Stop时返回的arrayBuffer的长度将为0字节

					//如果是H5环境，也可以直接构造成Blob/File文件对象，和Recorder使用一致
					// #ifdef H5
					var blob = new Blob([arrayBuffer], { type: mime });
					console.log(blob, (window.URL || webkitURL).createObjectURL(blob));
					var file = new File([arrayBuffer], 'recorder.wav');
					//uni.uploadFile({file:file, ...}) //参考demo中的test_upload_saveFile.vue
					this.upFile(file);
					// #endif

					//如果是App、小程序环境，可以直接保存到本地文件，然后调用相关网络接口上传
					// #ifdef APP || MP-WEIXIN
					RecordApp.UniSaveLocalFile(
						'recorder.wav',
						arrayBuffer,
						(savePath) => {
							console.log(savePath); //app保存的文件夹为`plus.io.PUBLIC_DOWNLOADS`，小程序为 `wx.env.USER_DATA_PATH` 路径
							//uni.uploadFile({filePath:savePath, ...}) //参考demo中的test_upload_saveFile.vue
						},
						(errMsg) => {
							console.error(errMsg);
						}
					);
					// #endif
				},
				(msg) => {
					console.error('结束录音失败：' + msg);
				}
			);
		}
	}
};
</script>

<style scoped lang="scss">
.tn-btn {
	width: auto !important;
	padding: 10rpx 20rpx !important;
}
</style>
